/*
 * Copyright 2015-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ch.app.test.qianzhiku.demo;

import ch.qos.logback.classic.Logger;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 验签demo.
 * @author CJSHEN
 * @since 1.0
 */
public class CheckSignDemo {

    private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(CheckSignDemo.class);

    private static final String RSA_ALGORITHM = "RSA";

    private static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";

    /**privateKey调用接口应用生产验签使用.*/
    private static final String PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAITf1S8w6M/5qEap4TJ4eQPzaD1Z"
            + "W98N/QbsvPd0I0vrZWxCRVUHokuYmM1oNTcRvo19NxyAQXwvJep55CfZ2BXx540MY4+mpV21JiZda75Q0rrVUMOHs+rvf/9ZcMX2LQMhxKTCNl"
            + "BFfG6IV1NeBvmENFteEq0XSFCikz/yZ6w5AgMBAAECgYBmvO8IEwggLshXAuk17gKb9Gtn7BzmBdnbrdaaerkD7Zm5MDCNg16Qlwem4ukqkFTG"
            + "78daCS0ZCReHCgJIcd7qUI1Bsq98CXAGXgypTyaqtQLKzZCmmu8LEv+FNG3BkCWccbZt6do6Ld62ZuSwIGgISazmPjnvv+lYcBu1aehRgQJBAN"
            + "XfylNmUaWrciGrklu31Ry3iFt+KgbI7NUOviyabrW2MzReqbicOE5DXKW2NVYe/Jw0BHBgqsmN9HpSgs3OGIkCQQCfC8aVJ4tDspy6qXgQovjp"
            + "bhtCl4aWAHAz10Is+pqn9WCCRsGPOgO3gKpaAKu1bSuX4Cs5FypSP8BQ3scl2qoxAkB3AOjhcwNnPXhqB7+OTUHFktHKpu7Xi6FMA8IJq3yCm3"
            + "juYOXrFPG3in/YrQQMgX1ppP/7T654LAamsZaa0VyRAkAJYwyJaRgTGsudSLtJYBUI0fLR71eGKuh0ZtQFC5VtLFJR3hM2fNoTToi/XyLDRno4"
            + "MIbci713tUOIgl2Gom1BAkA86OUwMOj3MdlfuOFjgqtlBktl6u05M1ZkHJaGVnJ3d8OVNcp6T1E8yB9G3HZnNYhzRMW+tuHkZrltljgwz+iI";

    /**publicKey前置库统一数据写入接口验签使用.*/
    private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCE39UvMOjP+ahGqeEyeHkD82g9WVvfDf0G7Lz3dCNL"
            + "62VsQkVVB6JLmJjNaDU3Eb6NfTccgEF8LyXqeeQn2dgV8eeNDGOPpqVdtSYmXWu+UNK61VDDh7Pq73//WXDF9i0DIcSkwjZQRXxuiFdTXgb5hDRbX"
            + "hKtF0hQopM/8mesOQIDAQAB";

    /**
     * 验签生成.
     * @param content  待生产验签内容
     * @param privateKey 私钥
     * @param encode  编码
     * @return String
     */
    public static String sign(final String content, final String privateKey, final String encode) {
        try {
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
            KeyFactory keyf = KeyFactory.getInstance(RSA_ALGORITHM);
            PrivateKey priKey = keyf.generatePrivate(priPKCS8);

            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(priKey);
            signature.update(content.getBytes(encode));

            byte[] signed = signature.sign();
            return Base64.encodeBase64URLSafeString(signed);
        } catch (final Exception e) {
            LOGGER.error("签名发生异常", e);
        }
        return null;
    }

    /**
     * 验证签名.
     * @param content 待验签内容
     * @param sign 签名
     * @param publicKey 公钥
     * @param encode 编码
     * @return boolean
     */
    public static boolean verify(final String content, final String sign, final String publicKey, final String encode)  {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            byte[] encodedKey = Base64.decodeBase64(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(pubKey);
            signature.update(content.getBytes(encode));

            return signature.verify(Base64.decodeBase64(sign));
        } catch (final Exception e) {
            LOGGER.error("验签发生异常", e);
        }
        return false;
    }

    /**
     * 由于json字符串过长而且经常变化,所以读文件,没有其他用意!.
     * @param filePath filePath
     * @return String
     */
    private static String readToString(final String filePath) {
        String encoding = "GBK";
        File file = new File(filePath);
        Long filelength = file.length();
        byte[] filecontent = new byte[filelength.intValue()];
        try {
            FileInputStream in = new FileInputStream(file);
            in.read(filecontent);
            in.close();
        }  catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        try {
            return new String(filecontent, encoding);
        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取待验签的字符串.
     * @param params params
     * @return signStr
     */
    private static String getSignStr(final Map<String, Object> params) {
        Map<String, Object> result = new LinkedHashMap<>();

        params.entrySet().stream()
                .filter(e -> e.getValue() != null && !"".equals(e.getValue()) && !"sign".equals(e.getKey()))
                .sorted(Map.Entry.<String, Object>comparingByKey())
                .forEachOrdered(e -> result.put(e.getKey(), e.getValue()));

        StringBuilder signStr = new StringBuilder("");
        result.forEach((key, value) -> signStr.append(key).append("=").append(value).append("&"));
        signStr.deleteCharAt(signStr.length()-1);
        System.out.println(signStr.toString());
        return signStr.toString();
    }



    /**
     * 非对称加密
     * 验签生产测试.
     * @param args args
     */
    public static void main(final String[] args) {
        //请求参数
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("content", ""); //json字符串,写入前置库数据
        params.put("bizType", "taking"); //业务类型 taking handon signature...


        /*bizType 业务类型字段值

        揽收  taking
        派件  handon
        签收  signature
        容器  container
        外场揽收  receiveTaking
        到发车  truck
        快递柜  stage
        留仓件  stay
        退回件  return
                单据  bill
        地磅称重  loadometer
        操作见过检查  checkresult*/

        params.put("appId", "front-api-test");  //在前置库申请的应用名称
        params.put("charset", "UTF-8"); //验签参数编码字符集
        params.put("signType", "RSA"); //目前只支持RSA的验签方式
        params.put("timestamp", "2019-08-01 10:10:53.993"); //当前时间戳格式 yyyy-MM-dd HH:mm:ss.SSS
        params.put("version", "1.0"); //所有服务器端用户版本号1.0

        //1,筛选,获取所有请求参数，不包括字节类型参数，如文件、字节流，剔除sign字段，剔除值为空的参数
        //2,排序,按照第一个字符的键值ASCII码递增排序（字母升序排序），如果遇到相同字符则按照第二个字符的键值ASCII码递增排序，以此类推
        //3,拼接,将排序后的参数与其对应值，组合成“参数=参数值”的格式，并且把这些参数用&字符连接起来，此时生成的字符串为待签名字符串
        String signStr = getSignStr(params);

        //4,调用签名函数,使用各自语言对应的SHA1WithRSA(对应signType为RSA)签名函数利用应用私钥对待签名字符串进行签名，并进行Base64编码
        String sign = sign(signStr, PRIVATE_KEY, String.valueOf(params.get("charset")));
        //LOGGER.info("sign="+sign);
        System.out.println("sign="+sign);

        //5,前置库统一数据接入接口验签(调用接口应用忽略此步骤)。
        boolean isSuccess = verify(signStr, sign, PUBLIC_KEY, String.valueOf(params.get("charset")));
        //LOGGER.info("验签结果:"+isSuccess);
        System.out.println("验签结果:"+isSuccess);

        System.out.println("OTPDX".startsWith("OTP_"));


    }
}
